Skip to content

fix: Node low-severity security hardening bundle (timing oracle, sharp DoS, dependabot, deps, audit)#3876

Merged
PierreBrisorgueil merged 7 commits into
masterfrom
fix/node-hardening-bundle
Jun 15, 2026
Merged

fix: Node low-severity security hardening bundle (timing oracle, sharp DoS, dependabot, deps, audit)#3876
PierreBrisorgueil merged 7 commits into
masterfrom
fix/node-hardening-bundle

Conversation

@PierreBrisorgueil

@PierreBrisorgueil PierreBrisorgueil commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Items

5.1 — Account-enumeration timing oracle (auth)
Equalise the login response time on the unknown-user path by running a dummy bcrypt.compare against a static hash when no user record is found. Previously the handler returned immediately on a DB miss, leaking user existence via response-time difference.

5.2 — Sharp CPU-exhaustion DoS (uploads)
Rate-limit the public image endpoint (GET /uploads/:id/image) via a dedicated publicImage limiter profile added to the base config layer, with a tighter production override. No dimension clamping is added — file size is already allowlisted at the storage layer.

5.3 — Dependabot auto-merge scope (CI)
Restrict the Dependabot auto-merge workflow to version-update:semver-patch updates only. Minor and major bumps now require a human review before merge.

5.4 — devDependency hygiene (deps)
Move test and build-only tooling (cross-env intentionally kept — runtime script use; snyk intentionally kept — CI script use) to devDependencies. Reduces the production install footprint.

5.5 — npm audit fix (deps)
npm audit fix cleared all remaining advisories via in-range lockfile bumps. A note has been added to ERRORS.md documenting the residual reasoning for any advisory that was explicitly accepted.

Scope

  • Module(s) impacted: modules/auth, modules/uploads, .github/workflows, package.json, package-lock.json, ERRORS.md
  • Cross-module impact: none
  • Risk level: low

Validation

  • npm run lint
  • npm test
  • Manual checks done (if applicable)

Guardrails check

  • No secrets or credentials introduced (.env*, secrets/**, keys, tokens)
  • No risky rename/move of core stack paths
  • Changes remain merge-friendly for downstream projects
  • Tests added or updated when behavior changed

Optional: Infra/Stack alignment details

Before vs After (key changes only)

Area Before After Notes
Unknown-user login Returns immediately (timing leak) Runs dummy bcrypt compare Account enumeration hardening
Public image endpoint No rate limit publicImage limiter profile CPU-exhaustion DoS mitigation
Dependabot auto-merge All semver levels Patch only Minor/major now require human review
devDependencies Several test tools in dependencies Moved to devDependencies Smaller prod install
npm audit Advisories present All cleared In-range lockfile bumps only
  • Upstream parity target / source: N/A — security hardening applied to stack directly
  • Automation or policy impact (CI, Dependabot, auto-merge, majors, permissions): Dependabot auto-merge now patch-only
  • Rollback plan: Revert individual commits — each item is an isolated commit

Notes for reviewers

  • Security considerations: All items are low-severity. No breaking changes. Timing equalisation uses the same bcrypt cost factor as the production hash to ensure the dummy compare takes comparable wall time.
  • Mergeability considerations: npm audit fix changed package-lock.json — CI will run npm ci cleanly on the updated lockfile.
  • Follow-up tasks (optional): Remaining higher-severity items (S1–S4) are tracked under epic 🎯 Security audit hardening — Node + Vue (2026-06 audit) #3848.

Summary by CodeRabbit

  • New Features

    • Rate limiting added to public image requests to prevent abuse and improve service stability.
  • Bug Fixes

    • Enhanced authentication security with improved consistency.
  • Documentation

    • Added dependency audit remediation guidelines.
  • Chores

    • Reorganized test-related dependencies as development-only.

@PierreBrisorgueil PierreBrisorgueil added the Fix A bug fix label Jun 15, 2026
@PierreBrisorgueil PierreBrisorgueil self-assigned this Jun 15, 2026
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PierreBrisorgueil, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 46 minutes and 28 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ef27bcb-d444-4064-97d5-5451f1ff4e87

📥 Commits

Reviewing files that changed from the base of the PR and between 28a9fd6 and ecde5b9.

📒 Files selected for processing (2)
  • modules/auth/tests/auth.unit.tests.js
  • modules/uploads/routes/uploads.routes.js

Walkthrough

Three security fixes are applied: authenticate() now runs a dummy bcrypt compare against a sentinel hash when no user is found, equalizing timing with the wrong-password path. The public /api/uploads/images/:imageName route gains a publicImage rate-limiter middleware backed by per-environment config. Dependabot auto-merge is restricted to semver-patch only, test packages are moved to devDependencies, and an ERRORS.md entry documents npm audit remediation.

Changes

Security hardening bundle

Layer / File(s) Summary
Constant-time auth for unknown users
modules/auth/services/auth.service.js, modules/auth/tests/auth.unit.tests.js
Adds DUMMY_PASSWORD_HASH sentinel constant and rewires the unknown-user branch of authenticate() to perform a dummy comparePassword call before throwing, matching timing of the known-user wrong-password path. Unit test added for the unknown-email scenario asserting bcrypt compare is called exactly once.
Rate limiter on public image route
modules/uploads/config/uploads.development.config.js, config/defaults/production.config.js, config/defaults/test.config.js, modules/uploads/routes/uploads.routes.js, lib/middlewares/tests/rateLimiter.baseLayer.unit.tests.js, lib/middlewares/tests/rateLimiter.unit.tests.js
Defines the publicImage rate-limit profile in dev (1-min window), production (60 req/60s), and test (unbounded) configs. Imports limiters in the uploads route and inserts limiters.publicImage middleware before uploads.getSharp. Base-layer test verifies the profile exists with valid numeric options; middleware unit test confirms the profile resolves to a real express-rate-limit instance.
Dependabot scope restriction and dependency reclassification
.github/workflows/dependabot.yml, package.json, ERRORS.md
Restricts Dependabot auto-approve/auto-merge to version-update:semver-patch only (removes minor). Moves @jest/globals, jest-environment-jsdom, nodemon, supertest, and ts-jest from dependencies to devDependencies. Adds an ERRORS.md entry documenting npm audit advisory handling.

Sequence Diagram(s)

Constant-time authenticate() flow

sequenceDiagram
  participant Client
  participant AuthService as auth.service.js
  participant UserRepo as getBrut
  participant bcrypt as comparePassword

  Client->>AuthService: authenticate(email, password)
  AuthService->>UserRepo: getBrut(email)
  alt user record found
    UserRepo-->>AuthService: userRecord
    AuthService->>bcrypt: comparePassword(password, userRecord.hash)
    bcrypt-->>AuthService: true / false
  else user not found
    UserRepo-->>AuthService: null
    AuthService->>bcrypt: comparePassword(password, DUMMY_PASSWORD_HASH)
    bcrypt-->>AuthService: dummy result (discarded)
    AuthService-->>Client: AppError("invalid user or password")
  end
Loading

Public image request with rate limiting

sequenceDiagram
  participant Guest as Guest Client
  participant Route as uploads.routes.js
  participant Limiter as limiters.publicImage
  participant Sharp as uploads.getSharp

  Guest->>Route: GET /api/uploads/images/:imageName
  Route->>Limiter: policy.isAllowed → limiters.publicImage
  alt requests within limit
    Limiter-->>Sharp: next()
    Sharp-->>Guest: processed image
  else rate limit exceeded
    Limiter-->>Guest: 429 Too Many Requests
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

security, Severity5: access/security

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes all five key security hardening improvements included in the changeset: timing oracle fix, sharp DoS mitigation, Dependabot restriction, dependency hygiene, and audit clearance.
Description check ✅ Passed The PR description comprehensively covers all required template sections with detailed explanations of each security hardening item, clear scope definition, completed validation checklist, guardrails verification, and rollback plan.
Linked Issues check ✅ Passed All five requirements from issue #3853 are addressed: timing oracle fixed via dummy bcrypt compare, sharp DoS mitigated with publicImage rate limiter, Dependabot restricted to patch-only, dev tooling moved to devDependencies, and audit advisories cleared with documentation.
Out of Scope Changes check ✅ Passed All changes directly correspond to the five security hardening items in issue #3853; no unrelated modifications to auth logic, rate-limiting configuration, CI workflows, or dependencies were introduced.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/node-hardening-bundle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@PierreBrisorgueil PierreBrisorgueil marked this pull request as ready for review June 15, 2026 09:12
Copilot AI review requested due to automatic review settings June 15, 2026 09:12
@codecov

codecov Bot commented Jun 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.50%. Comparing base (edf5b93) to head (ecde5b9).

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3876   +/-   ##
=======================================
  Coverage   92.50%   92.50%           
=======================================
  Files         165      165           
  Lines        5400     5403    +3     
  Branches     1739     1740    +1     
=======================================
+ Hits         4995     4998    +3     
  Misses        325      325           
  Partials       80       80           
Flag Coverage Δ
integration 60.11% <100.00%> (+0.02%) ⬆️
unit 73.68% <100.00%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update edf5b93...ecde5b9. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@PierreBrisorgueil

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 15, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
modules/uploads/routes/uploads.routes.js (1)

10-13: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add required JSDoc tags for the modified route registrar function.

This function was modified, but the current header lacks @param and @returns.

JSDoc compliance patch
 /**
- * Routes
+ * Register uploads routes.
+ * `@param` {import('express').Application} app - Express app instance
+ * `@returns` {void}
  */
 export default (app) => {

As per coding guidelines, “Every new or modified function must have a JSDoc header: one-line description, @param for each argument, @returns for any non-void return value.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/uploads/routes/uploads.routes.js` around lines 10 - 13, The default
export function in uploads.routes.js lacks the required JSDoc documentation. Add
a `@param` tag to document the app parameter and a `@returns` tag to document the
function's return value. The JSDoc header should be placed directly above the
function declaration and include a brief one-line description followed by these
parameter and return tags as per the coding guidelines requiring all new or
modified functions to have complete JSDoc headers.

Source: Coding guidelines

modules/auth/services/auth.service.js (1)

94-112: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Timing still diverges between unknown-user and wrong-password failures.

Line 99 throws immediately after bcrypt for unknown users, but Line 111 awaits recordFailedAttempt(user) for known users. That DB write/read path keeps a measurable timing delta, so enumeration risk remains despite the dummy compare. Equalize the full failure path (not only bcrypt work).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@modules/auth/services/auth.service.js` around lines 94 - 112, The
authentication failure paths currently have different timing characteristics:
the unknown user path (line 99) throws immediately after the dummy bcrypt
compare, while the wrong password path (line 111) awaits recordFailedAttempt
which performs a database operation. This timing delta allows attackers to
distinguish between unknown users and known users, defeating the
anti-enumeration goal. Add a dummy database operation or equivalent
timing-consuming operation in the unknown user path (after the comparePassword
call at line 99 and before throwing the error) so that both failure scenarios
(unknown user and wrong password) incur the same measurable latency from both
bcrypt work and database operations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@modules/auth/tests/auth.unit.tests.js`:
- Around line 100-108: The test for the unknown-user anti-enumeration path
currently only asserts that mockBcryptCompare was called once on line 107, but
does not validate the arguments passed to it. Strengthen the regression guard by
adding an additional expectation that verifies the second argument (the
sentinel/dummy hash) passed to mockBcryptCompare.toHaveBeenCalledWith, ensuring
that a proper bcrypt sentinel hash is being used rather than allowing
regressions to non-bcrypt values.

---

Outside diff comments:
In `@modules/auth/services/auth.service.js`:
- Around line 94-112: The authentication failure paths currently have different
timing characteristics: the unknown user path (line 99) throws immediately after
the dummy bcrypt compare, while the wrong password path (line 111) awaits
recordFailedAttempt which performs a database operation. This timing delta
allows attackers to distinguish between unknown users and known users, defeating
the anti-enumeration goal. Add a dummy database operation or equivalent
timing-consuming operation in the unknown user path (after the comparePassword
call at line 99 and before throwing the error) so that both failure scenarios
(unknown user and wrong password) incur the same measurable latency from both
bcrypt work and database operations.

In `@modules/uploads/routes/uploads.routes.js`:
- Around line 10-13: The default export function in uploads.routes.js lacks the
required JSDoc documentation. Add a `@param` tag to document the app parameter and
a `@returns` tag to document the function's return value. The JSDoc header should
be placed directly above the function declaration and include a brief one-line
description followed by these parameter and return tags as per the coding
guidelines requiring all new or modified functions to have complete JSDoc
headers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 86331033-d956-41e8-8725-dd747228c8d7

📥 Commits

Reviewing files that changed from the base of the PR and between edf5b93 and 28a9fd6.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • .github/workflows/dependabot.yml
  • ERRORS.md
  • config/defaults/production.config.js
  • config/defaults/test.config.js
  • lib/middlewares/tests/rateLimiter.baseLayer.unit.tests.js
  • lib/middlewares/tests/rateLimiter.unit.tests.js
  • modules/auth/services/auth.service.js
  • modules/auth/tests/auth.unit.tests.js
  • modules/uploads/config/uploads.development.config.js
  • modules/uploads/routes/uploads.routes.js
  • package.json

Comment thread modules/auth/tests/auth.unit.tests.js
@PierreBrisorgueil

Copy link
Copy Markdown
Contributor Author

Addressing the two outside-diff CodeRabbit findings from the CHANGES_REQUESTED review (ecde5b9):

1. JSDoc on uploads.routes.js (fixed in ecde5b9): Added @param and @returns tags to the default export. The stub comment was a pre-existing gap exposed by the modification — now compliant.

2. Timing delta from recordFailedAttempt (informational — tracking as follow-up): Valid point. The unknown-user path currently runs only the dummy bcrypt compare while the wrong-password path additionally runs recordFailedAttempt (2 DB ops). However:

  • The bcrypt equalization is the primary and dominant mitigation (100s of ms vs 1–5ms for DB ops)
  • Full timing equalization would require a dummy no-op DB read on the unknown-user path, which is a separate design decision
  • Network jitter in typical deployments masks sub-10ms deltas
  • This was not in the committed scope of issue 🔒 Node hardening: timing oracle, sharp DoS, dependabot, prod deps #3853 item 5.1

A follow-up issue will be filed on this repo to track the complete timing equalization improvement.

@PierreBrisorgueil

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@PierreBrisorgueil PierreBrisorgueil dismissed coderabbitai[bot]’s stale review June 15, 2026 09:26

All actionable issues addressed: inline nitpick (test assertion) fixed in ec675a0; JSDoc compliance fixed in ecde5b9; timing-delta comment addressed as informational with follow-up issue planned. CodeRabbit re-review confirmed no new issues.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@PierreBrisorgueil PierreBrisorgueil merged commit 3937953 into master Jun 15, 2026
8 checks passed
@PierreBrisorgueil PierreBrisorgueil deleted the fix/node-hardening-bundle branch June 15, 2026 09:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

🔒 Node hardening: timing oracle, sharp DoS, dependabot, prod deps

2 participants